﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using JY.Common.EntityBase;
using JY.Common.Repository;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;

namespace JY.Repository.EntityFrameworkCore
{
    /// <summary>
    /// 工作单元
    /// </summary>
    public class UnitOfWorkOfCommand : IUnitOfWorkOfCommand
    {
        #region # 变量
        /// <summary>
        /// EF（写）上下文对象字段
        /// </summary>
        protected readonly DbContext CommandDbContext;
        /// <summary>
        /// 同步锁
        /// </summary>
        private static readonly object Sync;
        #endregion

        #region # 构造器
        /// <summary>
        /// 静态构造器
        /// </summary>
        static UnitOfWorkOfCommand()
        {
            Sync = new object();
        }
        /// <summary>
        /// 构造器
        /// </summary>
        public UnitOfWorkOfCommand()
        {
            this.CommandDbContext = DbContextBase.CommandInstance;
        }
        #endregion

        #region # 析构器

        /// <summary>
        /// 析构器
        /// </summary>
        ~UnitOfWorkOfCommand()
        {
            this.Dispose();
        }

        #endregion

        #region # 释放资源 —— void Dispose()
        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            this.CommandDbContext?.Dispose();
        }
        #endregion

        #region # 命令部分

        #region # 注册添加单个实体对象 —— void RegisterAdd<T>(T entity)
        /// <summary>
        /// 注册添加单个实体对象
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="entity">新实体对象</param>
        public void RegisterAdd<T>(T entity) where T : PlainEntity
        {
            #region # 验证

            if (entity == null)
            {
                throw new ArgumentNullException(nameof(entity), $"要添加的{typeof(T).Name}实体对象不可为空！");
            }

            #endregion

            this.CommandDbContext.Set<T>().Add(entity);
        }
        #endregion

        #region # 注册添加实体对象列表 —— void RegisterAddRange<T>(IEnumerable<T> entities)
        /// <summary>
        /// 注册添加实体对象列表
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="entities">实体对象列表</param>
        public void RegisterAddRange<T>(IEnumerable<T> entities) where T : PlainEntity
        {
            #region # 验证

            entities = entities?.ToArray() ?? new T[0];
            if (!entities.Any())
            {
                throw new ArgumentNullException(nameof(entities), $"要添加的{typeof(T).Name}实体对象列表不可为空！");
            }

            #endregion

            this.CommandDbContext.Set<T>().AddRange(entities);
        }
        #endregion

        #region # 注册保存单个实体对象 —— void RegisterSave<T>(T entity)
        /// <summary>
        /// 注册保存单个实体对象
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="entity">实体对象</param>
        public void RegisterSave<T>(T entity) where T : PlainEntity
        {
            #region # 验证

            if (entity == null)
            {
                throw new ArgumentNullException(nameof(entity), $"要保存的{typeof(T).Name}实体对象不可为空！");
            }

            #endregion
            EntityEntry entry = this.CommandDbContext.Entry<T>(entity);
            entry.State = EntityState.Modified;
        }
        #endregion

        #region # 注册保存实体对象列表 —— void RegisterSaveRange<T>(IEnumerable<T> entities)
        /// <summary>
        /// 注册保存实体对象列表
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="entities">实体对象列表</param>
        public void RegisterSaveRange<T>(IEnumerable<T> entities) where T : PlainEntity
        {
            #region # 验证

            entities = entities?.ToArray() ?? new T[0];
            if (!entities.Any())
            {
                throw new ArgumentNullException(nameof(entities), $"要保存的{typeof(T).Name}实体对象列表不可为空！");
            }

            #endregion
            foreach (T entity in entities)
            {
                EntityEntry entry = this.CommandDbContext.Entry<T>(entity);
                entry.State = EntityState.Modified;
            }
        }
        #endregion

        #region # 注册删除单个实体对象（物理删除） —— void RegisterPhysicsRemove<T>(Guid id)
        /// <summary>
        /// 注册删除单个实体对象（物理删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="id">标识Id</param>
        public void RegisterPhysicsRemove<T>(Guid id) where T : PlainEntity
        {
            T entity = this.CommandDbContext.Set<T>().Single(x => x.Id == id);
            this.CommandDbContext.Set<T>().Remove(entity);
        }
        #endregion

        #region # 注册删除单个实体对象（物理删除） —— void RegisterPhysicsRemove<T>(string number)
        /// <summary>
        /// 注册删除单个实体对象（物理删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="number">编号</param>
        public void RegisterPhysicsRemove<T>(string number) where T : RootEntity
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(number))
            {
                throw new ArgumentNullException(nameof(number), $"要删除的{typeof(T).Name}实体对象编号不可为空！");
            }

            #endregion

            T entity = this.CommandDbContext.Set<T>().Single(x => x.Number == number);
            this.CommandDbContext.Set<T>().Remove(entity);
        }
        #endregion

        #region # 注册删除单个实体对象（物理删除） —— void RegisterPhysicsRemove<T>(T entity)
        /// <summary>
        /// 注册删除单个实体对象（物理删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="entity">实体对象</param>
        public void RegisterPhysicsRemove<T>(T entity) where T : PlainEntity
        {
            this.CommandDbContext.Set<T>().Remove(entity);
        }
        #endregion

        #region # 注册删除多个实体对象（物理删除） —— void RegisterPhysicsRemoveRange<T>(IEnumerable<Guid> ids)
        /// <summary>
        /// 注册删除多个实体对象（物理删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="ids">标识Id集</param>
        public void RegisterPhysicsRemoveRange<T>(IEnumerable<Guid> ids) where T : PlainEntity
        {
            #region # 验证

            ids = ids?.ToArray() ?? new Guid[0];
            if (!ids.Any())
            {
                throw new ArgumentNullException(nameof(ids), $"要删除的{typeof(T).Name}的Id集不可为空！");
            }

            #endregion

            ICollection<T> entities = this.ResolveRange<T>(ids);
            foreach (T entity in entities)
            {
                this.CommandDbContext.Set<T>().Remove(entity);
            }
        }
        #endregion

        #region # 注册删除单个实体对象（逻辑删除） —— void RegisterRemove<T>(Guid id)
        /// <summary>
        /// 注册删除单个实体对象（逻辑删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="id">标识Id</param>
        public void RegisterRemove<T>(Guid id) where T : RootEntity
        {
            T entity = this.Resolve<T>(x => x.Id == id);

            #region # 验证为null

            if (entity == null)
            {
                throw new NullReferenceException($"Id为\"{id}\"的{typeof(T).Name}实体不存在！");
            }

            #endregion

            entity.SetDeleted();
            EntityEntry entry = this.CommandDbContext.Entry<T>(entity);
            entry.State = EntityState.Modified;
        }
        #endregion

        #region # 注册删除单个实体对象（逻辑删除） —— void RegisterRemove<T>(string number)
        /// <summary>
        /// 注册删除单个实体对象（逻辑删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="number">编号</param>
        public void RegisterRemove<T>(string number) where T : RootEntity
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(number))
            {
                throw new ArgumentNullException(nameof(number), $"要删除的{typeof(T).Name}实体对象编号不可为空！");
            }

            #endregion

            T entity = this.Resolve<T>(x => x.Number == number);

            #region # 验证为null

            if (entity == null)
            {
                throw new NullReferenceException($"编号为\"{number}\"的{typeof(T).Name}实体不存在！");
            }

            #endregion

            entity.SetDeleted();
            EntityEntry entry = this.CommandDbContext.Entry<T>(entity);
            entry.State = EntityState.Modified;
        }
        #endregion

        #region # 注册删除单个实体对象（逻辑删除） —— void RegisterRemove<T>(T entity)
        /// <summary>
        /// 注册删除单个实体对象（逻辑删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="entity">实体对象</param>
        public void RegisterRemove<T>(T entity) where T : RootEntity
        {
            entity.SetDeleted();
            EntityEntry entry = this.CommandDbContext.Entry<T>(entity);
            entry.State = EntityState.Modified;
        }
        #endregion

        #region # 注册删除多个实体对象（逻辑删除） —— void RegisterRemoveRange<T>(IEnumerable<Guid> ids)
        /// <summary>
        /// 注册删除多个实体对象（逻辑删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="ids">标识Id集</param>
        public void RegisterRemoveRange<T>(IEnumerable<Guid> ids) where T : RootEntity
        {
            #region # 验证

            ids = ids?.ToArray() ?? new Guid[0];
            if (!ids.Any())
            {
                throw new ArgumentNullException(nameof(ids), $"要删除的{typeof(T).Name}的Id集不可为空！");
            }

            #endregion

            ICollection<T> entities = this.ResolveRange<T>(ids);
            foreach (T entity in entities)
            {
                EntityEntry entry = this.CommandDbContext.Entry<T>(entity);
                entity.SetDeleted();
                entry.State = EntityState.Modified;
            }
        }
        #endregion

        //Resolve部分

        #region # 根据条件获取唯一实体对象（修改时用） —— T ResolveOptional<T>(...
        /// <summary>
        /// 根据条件获取唯一实体对象（修改时用）
        /// </summary>
        /// <param name="predicate">条件</param>
        /// <returns>实体对象</returns>
        ///<remarks>查询不到将返回null</remarks>
        public T Resolve<T>(Expression<Func<T, bool>> predicate) where T : PlainEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), "条件表达式不可为空！");
            }

            #endregion

            var rootEntities = this.CommandDbContext.Set<T>().Where(x => (x is RootEntity));
            if (rootEntities.Any())
            {
                var rootEntities1 = rootEntities.Select(x => x as RootEntity).Where(x => !x.Deleted).AsQueryable();
                return rootEntities1.SingleOrDefault(predicate as Expression<Func<RootEntity, bool>>) as T;
            }
            return this.CommandDbContext.Set<T>().SingleOrDefault(predicate);
        }
        #endregion

        #region # 根据Id获取唯一实体对象（修改时用） —— T Resolve<T>(Guid id)
        /// <summary>
        /// 根据Id获取唯一实体对象（修改时用）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="id">标识Id</param>
        /// <returns>唯一实体对象</returns>
        public T Resolve<T>(Guid id) where T : PlainEntity
        {
            T entity = this.Resolve<T>(x => x.Id == id);

            #region # 验证为null

            if (entity == null)
            {
                throw new NullReferenceException($"Id为\"{id}\"的{typeof(T).Name}实体不存在！");
            }

            #endregion

            return entity;
        }
        #endregion

        #region # 根据Id集获取实体对象列表（修改时用） —— ICollection<T> ResolveRange<T>(...
        /// <summary>
        /// 根据Id集获取实体对象列表（修改时用）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="ids">标识Id集</param>
        /// <returns>实体对象列表</returns>
        public ICollection<T> ResolveRange<T>(IEnumerable<Guid> ids) where T : PlainEntity
        {
            return this.ResolveRange<T>(x => ids.Contains(x.Id)).ToListAsync().Result;
        }
        #endregion

        #region # 根据编号获取唯一实体对象（修改时用） —— T Resolve<T>(string number)
        /// <summary>
        /// 根据编号获取唯一实体对象（修改时用）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="number">编号</param>
        /// <returns>单个实体对象</returns>
        public T Resolve<T>(string number) where T : RootEntity
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(number))
            {
                throw new ArgumentNullException(nameof(number), $"{typeof(T).Name}的编号不可为空！");
            }

            #endregion

            T entity = this.Resolve<T>(x => x.Number == number);

            #region # 验证为null

            if (entity == null)
            {
                throw new NullReferenceException($"编号为\"{number}\"的{typeof(T).Name}实体不存在！");
            }

            #endregion

            return entity;
        }
        #endregion

        #region # 根据编号集获取实体对象列表（修改时用） —— ICollection<T> ResolveRange<T>(...
        /// <summary>
        /// 根据编号集获取实体对象列表（修改时用）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="numbers">编号集</param>
        /// <returns>实体对象列表</returns>
        public ICollection<T> ResolveRange<T>(IEnumerable<string> numbers) where T : RootEntity
        {
            return this.ResolveRange<T>(x => numbers.Contains(x.Number)).ToListAsync().Result;
        }
        #endregion

        #region # 异步根据Id获取唯一实体对象（修改时用） —— async Task<T> ResolveAsync<T>(Guid id)
        /// <summary>
        /// 异步根据Id获取唯一实体对象（修改时用）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="id">标识Id</param>
        /// <returns>唯一实体对象</returns>
        public async Task<T> ResolveAsync<T>(Guid id) where T : PlainEntity
        {
            T entity = await this.ResolveOptionalAsync<T>(x => x.Id == id);

            #region # 验证为null

            if (entity == null)
            {
                throw new NullReferenceException($"Id为\"{id}\"的{typeof(T).Name}实体不存在！");
            }

            #endregion

            return entity;
        }
        #endregion

        #region # 异步根据Id集获取实体对象列表（修改时用） —— async Task<ICollection<T>> ResolveRangeAsync<T>(...
        /// <summary>
        /// 异步根据Id集获取实体对象列表（修改时用）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="ids">标识Id集</param>
        /// <returns>实体对象列表</returns>
        public async Task<ICollection<T>> ResolveRangeAsync<T>(IEnumerable<Guid> ids) where T : PlainEntity
        {
            return await this.ResolveRange<T>(x => ids.Contains(x.Id)).ToListAsync();
        }
        #endregion

        #region # 异步根据编号获取唯一实体对象（修改时用） —— async Task<T> ResolveAsync<T>(string number)

        /// <summary>
        /// 异步根据编号获取唯一实体对象（修改时用）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="number">编号</param>
        /// <returns>单个实体对象</returns>
        public async Task<T> ResolveAsync<T>(string number) where T : RootEntity
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(number))
            {
                throw new ArgumentNullException(nameof(number), $"{typeof(T).Name}的编号不可为空！");
            }

            #endregion

            T entity = await this.ResolveOptionalAsync<T>(x => x.Number == number);

            #region # 验证为null

            if (entity == null)
            {
                throw new NullReferenceException($"编号为\"{number}\"的{typeof(T).Name}实体不存在！");
            }

            #endregion

            return entity;
        }
        #endregion

        #region # 异步根据编号集获取实体对象列表（修改时用） —— async Task<ICollection<T>> ResolveRangeAsync<T>(...
        /// <summary>
        /// 异步根据编号集获取实体对象列表（修改时用）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="numbers">编号集</param>
        /// <returns>实体对象列表</returns>
        public async Task<ICollection<T>> ResolveRangeAsync<T>(IEnumerable<string> numbers) where T : RootEntity
        {
            return await this.ResolveRange<T>(x => numbers.Contains(x.Number)).ToListAsync();
        }
        #endregion

        #region # 根据条件获取实体对象列表（修改时用） —— IQueryable<T> ResolveRange<T>(...
        /// <summary>
        /// 根据条件获取实体对象列表（修改时用）
        /// </summary>
        /// <param name="predicate">条件</param>
        /// <returns>实体对象列表</returns>
        public IQueryable<T> ResolveRange<T>(Expression<Func<T, bool>> predicate) where T : PlainEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), "条件表达式不可为空！");
            }

            #endregion

            var rootEntities = this.CommandDbContext.Set<T>().Where(x => (x is RootEntity));
            if (rootEntities.Any())
            {
                return rootEntities.Select(x => x as RootEntity).Where(x => !x.Deleted).Select(x=>x as T);
            }

            return this.CommandDbContext.Set<T>().Where(predicate);

        }
        #endregion


        //Commit部分

        #region # 统一事务处理保存更改 —— void Commit()
        /// <summary>
        /// 统一事务处理保存更改
        /// </summary>
        public void Commit()
        {
            try
            {
                //提交事务
                this.CommandDbContext.SaveChanges();
            }
            catch
            {
                this.RollBack();
                throw;
            }
        }
        #endregion

        #region # 统一事务处理保存更改（异步） —— async Task CommitAsync()
        /// <summary>
        /// 统一事务处理保存更改（异步）
        /// </summary>
        public async Task CommitAsync()
        {
            try
            {
                //提交事务
                await this.CommandDbContext.SaveChangesAsync();
            }
            catch
            {
                this.RollBack();
                throw;
            }
        }
        #endregion

        #region # 统一事务回滚取消更改 —— void RollBack()
        /// <summary>
        /// 统一事务回滚取消更改
        /// </summary>
        public void RollBack()
        {
            foreach (EntityEntry entry in this.CommandDbContext.ChangeTracker.Entries())
            {
                entry.State = EntityState.Unchanged;
            }
        }
        #endregion

        #region # 执行SQL命令（无需Commit） —— void ExecuteSqlCommand(string sql...
        /// <summary>
        /// 执行SQL命令（无需Commit）
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <param name="parameters">参数列表</param>
        public void ExecuteSqlCommand(string sql, params object[] parameters)
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(sql))
            {
                throw new ArgumentNullException(nameof(sql), "SQL语句不可为空！");
            }

            #endregion

            this.CommandDbContext.Database.ExecuteSqlRaw(sql, parameters);
        }
        #endregion

        #region # 异步执行SQL命令（无需Commit） —— Task ExecuteSqlCommandAsync(string sql...
        /// <summary>
        /// 异步执行SQL命令（无需Commit）
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <param name="parameters">参数</param>
        public async Task ExecuteSqlCommandAsync(string sql, params object[] parameters)
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(sql))
            {
                throw new ArgumentNullException(nameof(sql), "SQL语句不可为空！");
            }

            #endregion

            await this.CommandDbContext.Database.ExecuteSqlRawAsync(sql, parameters);
        }
        #endregion


        /**********Protected**********/

        #region # 注册条件删除（物理删除） —— void RegisterPhysicsRemove<T>(...
        /// <summary>
        /// 注册条件删除（物理删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="predicate">条件表达式</param>
        protected void RegisterPhysicsRemove<T>(Expression<Func<T, bool>> predicate) where T : RootEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), "条件表达式不可为空！");
            }

            #endregion

            IQueryable<T> queryable = this.CommandDbContext.Set<T>().Where(x => !x.Deleted).Where(predicate);

            foreach (T entity in queryable)
            {
                this.CommandDbContext.Set<T>().Attach(entity);
                this.CommandDbContext.Set<T>().Remove(entity);
            }
        }
        #endregion

        #region # 注册条件删除（逻辑删除） —— void RegisterRemove<T>(...
        /// <summary>
        /// 注册条件删除（逻辑删除）
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="predicate">条件表达式</param>
        protected void RegisterRemove<T>(Expression<Func<T, bool>> predicate) where T : RootEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), "条件表达式不可为空！");
            }

            #endregion

            IQueryable<T> queryable = this.CommandDbContext.Set<T>().Where(x => !x.Deleted).Where(predicate);

            foreach (T entity in queryable)
            {
                entity.SetDeleted();
                EntityEntry entry = this.CommandDbContext.Entry<T>(entity);
                entry.State = EntityState.Modified;
            }
        }
        #endregion

        #region # 异步根据条件获取唯一实体对象（修改时用） —— async Task<T> ResolveOptionalAsync<T>(...
        /// <summary>
        /// 异步根据条件获取唯一实体对象（修改时用）
        /// </summary>
        /// <param name="predicate">条件</param>
        /// <returns>实体对象</returns>
        ///<remarks>查询不到将返回null</remarks>
        protected async Task<T> ResolveOptionalAsync<T>(Expression<Func<T, bool>> predicate) where T : PlainEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), "条件表达式不可为空！");
            }

            #endregion

            var rootEntities = this.CommandDbContext.Set<T>().Where(x => (x is RootEntity));
            if (rootEntities.Any())
            {
                var rootEntities1 = rootEntities.Select(x => x as RootEntity).Where(x => !x.Deleted).AsQueryable();
                return rootEntities1.SingleOrDefaultAsync(predicate as Expression<Func<RootEntity, bool>>) as T;
            }
            return await this.CommandDbContext.Set<T>().SingleOrDefaultAsync(predicate);
        }
        #endregion

        #region # 是否存在给定条件的实体对象 —— bool Exists(Expression<Func<T, bool>> predicate)
        /// <summary>
        /// 是否存在给定条件的实体对象
        /// </summary>
        /// <param name="predicate">条件</param>
        /// <returns>是否存在</returns>
        protected bool Exists<T>(Expression<Func<T, bool>> predicate) where T : RootEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), "条件表达式不可为空！");
            }

            #endregion

            lock (Sync)
            {
                return this.CommandDbContext.Set<T>().Where(x => !x.Deleted).Any(predicate);
            }
        }
        #endregion
        #endregion
    }
}
